// @Author EthanScriptOn
// @Desc
package util

import (
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"strings"
)

// InsertAtFirst element is inserted into the head of the collection
func InsertAtFirst(slice interface{}, element interface{}) (interface{}, error) {
	return InsertAtIndex(slice, element, 0, false)
}

// InsertAtIndex element is inserted into the specified subscript location
func InsertAtIndex(slice interface{}, element interface{}, index int, cover bool) (interface{}, error) {
	sliceValue := reflect.ValueOf(slice)
	if sliceValue.Kind() != reflect.Slice {
		return nil, errors.New("input parameter type error")
	}
	length := sliceValue.Len()
	capacity := sliceValue.Cap()
	if capacity < index || capacity == length {
		newCapacityFactor := capacity
		if capacity < index {
			newCapacityFactor = index
		}
		computeLen := length + 1
		computeCapacityFactor := newCapacityFactor * 2
		if computeLen > computeCapacityFactor {
			computeCapacityFactor = computeLen
		}
		newSlice := reflect.MakeSlice(sliceValue.Type(), computeLen, computeCapacityFactor)
		newSlice.Index(index).Set(reflect.ValueOf(element))
		for i := 0; i < length; i++ {
			if i == index && cover {
				continue
			}
			if i >= index {
				newSlice.Index(i + 1).Set(sliceValue.Index(i))
			} else {
				newSlice.Index(i).Set(sliceValue.Index(i))
			}
		}
		return newSlice.Interface(), nil
	} else {
		sliceValue.SetLen(length + 1)
		sliceValue.Index(index).Set(reflect.ValueOf(element))
		return sliceValue.Interface(), nil
	}
}

// RemoveSliceValue Deletes an element from the collection
func RemoveSliceValue(slice interface{}, element interface{}) (interface{}, error) {
	sliceValue := reflect.ValueOf(slice)
	if sliceValue.Kind() != reflect.Slice {
		return nil, errors.New("input parameter type error")
	}
	length := sliceValue.Len()
	if length <= 0 {
		return slice, nil
	}
	capacity := sliceValue.Cap()
	newSlice := reflect.MakeSlice(sliceValue.Type(), length, capacity)
	length -= 1
	for i := 0; i <= length; i++ {
		if !reflect.DeepEqual(sliceValue.Index(i).Interface(), element) {
			newSlice.Index(i).Set(sliceValue.Index(i))
		}
	}
	return newSlice.Interface(), nil
}

// RemoveSliceCustomized Customize the logic to remove from the collection
func RemoveSliceCustomized(slice interface{}, customized func(int, interface{}) bool) (interface{}, error) {
	if customized == nil {
		return slice, errors.New("customized function is nil")
	}
	sliceValue := reflect.ValueOf(slice)
	if sliceValue.Kind() != reflect.Slice {
		return nil, errors.New("input parameter type error")
	}
	length := sliceValue.Len()
	if length <= 0 {
		return slice, nil
	}
	capacity := sliceValue.Cap()
	newSlice := reflect.MakeSlice(sliceValue.Type(), length, capacity)
	length -= 1
	for i := 0; i <= length; i++ {
		if !customized(i, sliceValue.Index(i).Interface()) {
			newSlice.Index(i).Set(sliceValue.Index(i))
		}
	}
	return newSlice.Interface(), nil
}

// MapValues Obtain the values of the map
func MapValues(mapRef interface{}) (interface{}, error) {
	refValue := reflect.ValueOf(mapRef)
	if refValue.Kind() == reflect.Ptr {
		refValue = refValue.Elem()
	}
	afferentKind := refValue.Kind()
	if afferentKind != reflect.Map {
		return nil, errors.New(fmt.Sprintf("type error: [%+v] non map", afferentKind.String()))
	}
	values := make([]interface{}, 0)
	for _, keyValue := range refValue.MapKeys() {
		value := refValue.MapIndex(keyValue)
		values = append(values, value.Interface())
	}
	return values, nil
}

// MapKeys Obtain the keys of the map
func MapKeys(mapRef interface{}) (interface{}, error) {
	refValue := reflect.ValueOf(mapRef)
	if refValue.Kind() == reflect.Ptr {
		refValue = refValue.Elem()
	}
	afferentKind := refValue.Kind()
	if afferentKind != reflect.Map {
		return nil, errors.New(fmt.Sprintf("type error: [%+v] non map", afferentKind.String()))
	}
	keys := make([]interface{}, 0)
	for _, keyValue := range refValue.MapKeys() {
		keys = append(keys, keyValue.Interface())
	}
	return keys, nil
}

// ConvertMap Convert to map
func ConvertMap(mapRef interface{}) (interface{}, error) {
	refValue := reflect.ValueOf(mapRef)
	if refValue.Kind() == reflect.Ptr {
		refValue = refValue.Elem()
	}
	afferentKind := refValue.Kind()
	if afferentKind != reflect.Map {
		return nil, errors.New(fmt.Sprintf("type error: [%+v] non map", afferentKind.String()))
	}
	getMap := make(map[interface{}]interface{})
	for _, keyValue := range refValue.MapKeys() {
		valValue := refValue.MapIndex(keyValue)
		getMap[keyValue.Interface()] = valValue.Interface()
	}
	return getMap, nil
}

// MergeMap Merge of maps
func MergeMap(mapOneRef, mapTwoRef interface{}) (interface{}, error) {
	var oneRefValue, twoRefValue reflect.Value
	if ptr, ok := mapOneRef.(reflect.Value); ok && ptr.Kind() == reflect.Ptr {
		oneRefValue = ptr.Elem()
	} else {
		oneRefValue = reflect.ValueOf(mapOneRef)
	}
	if oneRefValue.Kind() != reflect.Map {
		return nil, errors.New("type error: first argument non map")
	}
	if ptr, ok := mapTwoRef.(reflect.Value); ok && ptr.Kind() == reflect.Ptr {
		twoRefValue = ptr.Elem()
	} else {
		twoRefValue = reflect.ValueOf(mapTwoRef)
	}
	if twoRefValue.Kind() != reflect.Map {
		return nil, errors.New("type error: second argument non map")
	}
	if oneRefValue.Type().Key() != twoRefValue.Type().Key() ||
		oneRefValue.Type().Elem() != twoRefValue.Type().Elem() {
		return nil, errors.New("type error: maps have different key or value types")
	}
	resultType := reflect.MapOf(oneRefValue.Type().Key(), oneRefValue.Type().Elem())
	result := reflect.MakeMap(resultType)
	for _, key := range oneRefValue.MapKeys() {
		value := oneRefValue.MapIndex(key)
		result.SetMapIndex(key, value)
	}
	for _, key := range twoRefValue.MapKeys() {
		value := twoRefValue.MapIndex(key)
		result.SetMapIndex(key, value)
	}
	return result.Interface(), nil
}

// GetMethodNameWithMethod Get the function name
func GetMethodNameWithMethod(methodInter interface{}) string {
	pointer := reflect.ValueOf(methodInter).Pointer()
	name := runtime.FuncForPC(pointer).Name()
	if name == "" {
		return ""
	}
	if index := strings.LastIndex(name, "-fm"); index != -1 {
		name = name[:index]
	}
	lastDotIndex := strings.LastIndex(name, ".")
	if lastDotIndex != -1 {
		name = name[lastDotIndex+1:]
	} else {
		name = ""
	}
	return name
}

// GetReceiverNameWithMethod There are significant limitations
func GetReceiverNameWithMethod(methodInter interface{}) (receiverName string, err error) {
	methodType := reflect.TypeOf(methodInter)
	if methodType.Kind() != reflect.Func {
		return "", errors.New("expected a function")
	}
	if methodType.NumIn() <= 0 {
		return "", nil
	}
	receiverType := methodType.In(0)
	if receiverType.Kind() == reflect.Ptr {
		receiverType = receiverType.Elem()
	}
	return receiverType.Name(), nil
}

// GetMethodName Get the function name, from the stack
func GetMethodName() string {
	return GetMethodNameSkip(1)
}

// GetMethodNameSkip Get the function name
func GetMethodNameSkip(skip int) string {
	pc1, _, _, _ := runtime.Caller(skip)
	name := runtime.FuncForPC(pc1).Name()
	split := strings.Split(name, ".")
	if len(split) <= 0 {
		return ""
	}
	return split[len(split)-1]
}

// NewInstance Create an instance
func NewInstance(typ reflect.Type) reflect.Value {
	return reflect.New(typ).Elem()
}

// Assert Type assertions
func Assert(targetInstance interface{}, conversionType reflect.Type) (interface{}, error) {
	if !reflect.TypeOf(targetInstance).Implements(conversionType) {
		return nil, fmt.Errorf("the conversionInstance is %v not Implements %v", targetInstance, conversionType)
	}
	return targetInstance, nil
}

// IsType Type judgment
func IsType(value interface{}, targetType reflect.Type) bool {
	return reflect.TypeOf(value) == targetType
}

// GetType Get the value type
func GetType(value interface{}) reflect.Type {
	return reflect.TypeOf(value)
}

// AssertType Assertion type
func AssertType(value interface{}, targetType reflect.Type) {
	if !IsType(value, targetType) {
		panic(fmt.Sprintf("Expected type %v, but got %v", targetType, reflect.TypeOf(value)))
	}
}

// AssertTypeWithMessage The type of assertion with a hint
func AssertTypeWithMessage(value interface{}, targetType reflect.Type, message string) {
	if !IsType(value, targetType) {
		panic(fmt.Sprintf("%s: Expected type %v, but got %v", message, targetType, reflect.TypeOf(value)))
	}
}
